﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace DotNetCommon.Extensions;

/// <summary>
/// MethodInfo 的扩展类
/// </summary>
public static class MethodInfoExtensions
{
    private static readonly ConcurrentDictionary<(MethodInfo method, string map), string> methodNamesCache = [];
    private static readonly ConcurrentDictionary<MethodInfo, (string name, List<(string genetypeName, bool isGenericParameter, Type geneType)> genericParameters)> methodNamesCache2 = [];

    /// <summary>
    /// 获取方法的详细描述，返回格式: "返回值类型 类名.方法名&lt;泛型列表&gt;(参数列表)"，示例：
    /// <list type="bullet">
    /// <item>typeof(Person).GetMethod("Show") => string TestNamespace.Person.Show(int x, int y)</item>
    /// </list>
    /// </summary>
    /// <param name="methodInfo"></param>
    /// <returns></returns>
    public static string GetMethodFullName(this MethodInfo methodInfo) => GetMethodFullName(methodInfo, null);

    private static string GetMethodFullName(this MethodInfo methodInfo, Dictionary<string, string> genericMap)
    {
        if (methodInfo == null) return null;
        var map = "";
        if (genericMap.IsNotNullOrEmpty())
        {
            var keys = genericMap.Keys.OrderBy(i => i);
            foreach (var item in keys)
            {
                map += $",{item}={genericMap[item]}";
            }
            map = map.Trim(',');
        }
        return methodNamesCache.GetOrAdd((methodInfo, map), _ =>
        {
            var className = methodInfo.DeclaringType.GetClassFullName(genericMap);
            var methodName = methodInfo.Name;
            var returnName = methodInfo.ReturnType.GetClassFullName(genericMap).ToString();
            var genes = "";

            if (methodInfo.IsGenericMethod)
            {
                //泛型方法
                var types = methodInfo.GetGenericArguments();
                genes = types.Select(i => i.GetClassFullName(genericMap)).ToStringSeparated(", ");
                genes = $"<{genes}>";
            }

            var arguments = methodInfo.GetParameters();
            var arr = new string[arguments.Length];
            for (int i = 0; i < arguments.Length; i++)
            {
                var parameter = arguments[i];
                var parameterTypeName = parameter.ParameterType.GetClassFullName(genericMap);
                var parameterName = parameter.Name;
                arr[i] = $"{parameterTypeName} {parameterName}";
            }
            return $"{returnName} {className}.{methodName}{genes}({arr.ToStringSeparated(", ")})";
        });
    }

    /// <summary>
    /// 获取方法的详细描述，返回格式: "返回值类型 类名.方法名&lt;泛型列表&gt;(参数列表)"，示例：
    /// <list type="bullet">
    /// <item>typeof(Person).GetMethod("Show") => string TestNamespace.Person&lt;T>.Show&lt;T2>(T t1,T t2, int x),[("T", false, typeof(int)), ("T2", false, typeof(T2))]</item>
    /// </list>
    /// </summary>
    /// <param name="methodInfo"></param>
    /// <returns></returns>
    public static (string Name, List<(string name, bool isGeneric, Type type)> GenericTypes) GetMethodGenericFullName(this MethodInfo methodInfo)
    {
        if (methodInfo == null) return (null, null);
        return methodNamesCache2.GetOrAdd(methodInfo, _ =>
         {
             //获取方法的声明类
             var declareType = methodInfo.DeclaringType;
             //声明类的泛型具化字典
             var (typeName, parasMap) = declareType.GetClassGenericFullName();
             var list = parasMap.ToList();
             var dic = new Dictionary<string, string>();
             list.ForEach(i => dic.Add(i.name, i.type.GetClassFullName()));

             //找到原始的定义: 泛型类模板以及它的泛型方法定义
             //代表泛型类具化后的泛型方法定义
             MethodInfo defineMidMethod = null;
             //代表泛型类模板
             Type topDeclareType = null;
             if (methodInfo.IsGenericMethodDefinition)
             {
                 //此方法是一个方法的泛型定义,如: void Show<T>();
                 defineMidMethod = methodInfo;
             }
             else if (methodInfo.IsGenericMethod)
             {
                 //此方法就是一个具化的泛型方法,如: void Show<int>();
                 defineMidMethod = methodInfo.GetGenericMethodDefinition();
             }
             else
             {
                 //非泛型方法 但类可能是泛型或具化后的,如: List<Person>.Count() List<T>.Count()
                 defineMidMethod = methodInfo;
             }
             topDeclareType = declareType.IsGenericType || declareType.IsGenericTypeDefinition ? declareType.GetGenericTypeDefinition() : declareType;

             //找到最终的方法定义
             var ms = topDeclareType.GetMethods().Where(i => i.Name == methodInfo.Name && i.GetParameters().Length == methodInfo.GetParameters().Length && i.IsStatic == methodInfo.IsStatic).ToList();

             MethodInfo srcMethodDefine = null;
             if (ms.Count == 1)
             {
                 srcMethodDefine = ms[0];
             }
             else
             {
                 //一个一个比对
                 var midName = defineMidMethod.GetMethodFullName();
                 for (int i = 0; i < ms.Count; i++)
                 {
                     var m = ms[i];
                     var tmp = m.GetMethodFullName(dic);
                     if (tmp == midName)
                     {
                         srcMethodDefine = m;
                     }
                 }
             }
             var name = srcMethodDefine.GetMethodFullName();
             var defines = srcMethodDefine.GetGenericArguments();
             var args = methodInfo.GetGenericArguments();
             for (int j = 0; j < args.Length; j++)
             {
                 var arg = args[j];
                 list.Add((defines[j].Name, methodInfo.IsGenericMethodDefinition, arg));
             }
             return (name, list);
         });
    }

    private static readonly ConcurrentDictionary<MethodInfo, Func<object, object[], object>> _methodCompileCaches = [];
    /// <summary>
    /// 将方法的反射调用转为委托,通过编译表达式树实现, 示例:
    /// <code>
    /// public class Demo
    /// {
    ///     public string Show(int num)
    ///     {
    ///         return num + "1";
    ///     }
    /// }
    /// 
    /// var method = typeof(Demo).GetMethod("Show");
    /// var func = method.Compile();
    /// var res = func(new Demo(), [2]) as string;
    /// //输出: "21"
    /// </code>
    /// </summary>
    public static Func<object, object[], object> Compile(this MethodInfo methodInfo)
    {
        AssertUtil.NotNull(methodInfo);
        if (_methodCompileCaches.TryGetValue(methodInfo, out var func)) return func;

        return _methodCompileCaches.GetOrAdd(methodInfo, methodInfo =>
        {
            var paraObject = Expression.Parameter(typeof(object));
            var paraParas = Expression.Parameter(typeof(object[]));

            var arguments = methodInfo.GetParameters();

            if (methodInfo.IsStatic)
            {
                var paraParas2 = arguments.Select((i, idx) => Expression.Convert(Expression.ArrayIndex(paraParas, Expression.Constant(idx)), i.ParameterType)).ToList();
                if (methodInfo.ReturnType == typeof(void))
                {
                    return Expression.Lambda<Func<object, object[], object>>(Expression.Block(Expression.Call(null, methodInfo, paraParas2), Expression.Constant(null)), paraObject, paraParas).Compile();
                }
                return Expression.Lambda<Func<object, object[], object>>(Expression.Convert(Expression.Call(null, methodInfo, paraParas2), typeof(object)), paraObject, paraParas).Compile();
            }
            else
            {
                var paraObject2 = Expression.Convert(paraObject, methodInfo.DeclaringType);
                var paraParas2 = arguments.Select((i, idx) => Expression.Convert(Expression.ArrayIndex(paraParas, Expression.Constant(idx)), i.ParameterType)).ToList();
                if (methodInfo.ReturnType == typeof(void))
                {
                    return Expression.Lambda<Func<object, object[], object>>(Expression.Block(Expression.Call(paraObject2, methodInfo, paraParas2), Expression.Constant(null)), paraObject, paraParas).Compile();
                }
                return Expression.Lambda<Func<object, object[], object>>(Expression.Convert(Expression.Call(paraObject2, methodInfo, paraParas2), typeof(object)), paraObject, paraParas).Compile();
            }
        });
    }
}
